﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using System;

/// <summary>
/// 依赖于GridLayoutGroup控件
/// 控件部分属性限制，
/// 1.content的anchor在上边（垂直布局，水平布局在左边）
/// 2.item的anchor在上边（垂直布局，水平布局在左边）
/// 3.未使用对象池
/// 4.具体看例子就好——zyg20180903
/// </summary>
[RequireComponent(typeof(GridLayoutGroup))]
public class LoopDragList : MonoBehaviour {
    //数据
    private IList m_dataList;
    private List<LoopItemBase> m_loopItemList = new List<LoopItemBase>();

    private RectTransform m_scrollRectTrans;//scrollView
    private ScrollRect m_scrollRect;//scrollView
    private GridLayoutGroup m_grid;//grid
    public GameObject m_item;//item
    private Type m_loopItemDataType;
    private RectTransform m_contentRectTrans;

    private Vector2 m_scrollViewV2;//显示区域大小
    private int m_startIndex = 0;//既生成的第一个的item索引
    //private int m_ceilIndex = 0;//存在的item最上一个的序号
    //private int m_floorIndex = 0;//存在的item最下边一个的序号
    private int m_fillViewItemNum = 0;//填充view需要的item数量
    private int m_preparativeItemNum = 3;//预备的item数量

    private float m_itemWight = 0;//grid.cellSize.x + grid.spacing.x;
    private float m_itemHeight = 0;
    private int m_multiLine = 1;//多行多列

    private Vector2 m_startV2;

    private void Awake() {
        m_dataList = new List<object>();
        for (int i=0; i < 200; i++) {
            m_dataList.Add(i);
        }
        CreateLoopScroll(m_item, m_dataList, typeof( LoopItemBase), 190);
    }
    #region public
    /// <summary>
    /// 对外初始化入口
    /// </summary>
    /// <param name="item">列表item</param>
    /// <param name="dataList">列表数据List</param>
    public void CreateLoopScroll(GameObject item, IList dataList, Type t, int startIndex =0) {
        if (!typeof(LoopItemBase).IsAssignableFrom(t)) {
            throw new Exception("传入类型必须是LoopItemBase的派生类");
        }
        if (m_item == null) {
            Debug.LogError("item is null");
            return;
        }
        m_loopItemDataType = t;
        m_item = item;
        m_dataList = dataList;
        m_startIndex = startIndex;
        m_scrollRectTrans = transform.parent.GetComponent<RectTransform>();
        m_scrollRect = transform.parent.GetComponent<ScrollRect>();
        m_grid = transform.GetComponent<GridLayoutGroup>();
        m_grid.enabled = false;
        m_contentRectTrans = transform.GetComponent<RectTransform>();
        m_startV2 = m_contentRectTrans.anchoredPosition;

        m_scrollViewV2 = m_scrollRectTrans.rect.size;
        Debug.Log("m_scrollViewV2==" + m_scrollViewV2);
        m_itemWight = m_grid.cellSize.x + m_grid.spacing.x;
        m_itemHeight = m_grid.cellSize.y + m_grid.spacing.y;
        m_multiLine = m_grid.constraintCount > 0 ? m_grid.constraintCount : 1;
        m_preparativeItemNum = m_multiLine * m_preparativeItemNum;

        if (m_scrollRect.vertical) {
            m_fillViewItemNum = ((int)Mathf.Ceil(m_scrollViewV2.y / m_itemHeight)) * m_multiLine;//先算竖的
        }
        else if (m_scrollRect.horizontal) {
            m_fillViewItemNum = ((int)Mathf.Ceil(m_scrollViewV2.x / m_itemWight)) * m_multiLine;
        }
        if (m_scrollRect.vertical && m_scrollRect.horizontal) {
            Debug.LogError("不支持水平竖直方向同时滑动");
            return;
        }

        if (m_scrollRect.vertical) {
            if(m_contentRectTrans.anchorMin.y!=1 || m_contentRectTrans.anchorMax.y != 1) {
                Debug.LogError("竖直方向滑动,contentRT的contentRT.anchorMin.y和contentRT.anchorMax.y都必须为1");
                return;
            }
        }
        if (m_scrollRect.horizontal) {
            if (m_contentRectTrans.anchorMin.x != 0 || m_contentRectTrans.anchorMax.x != 0) {
                Debug.LogError("水平方向滑动,contentRT的contentRT.anchorMin.x和contentRT.anchorMax.x都必须为0");
                return;
            }
        }


        CreateInitItem();
        m_scrollRect.onValueChanged.AddListener((data) => { ScrollCallback(data); });
    }
    /// <summary>
    /// 移动到index的item
    /// 此接口用于已经创建列表以后再定位的，
    /// 创建列表直接定位，请使用CreateLoopScroll
    /// </summary>
    /// <param name="index"></param>
    public void MoveToIndex(int index) {
        RemoveAllItem();
        //没有使用对象池，这样做比较省事
        CreateLoopScroll(m_item, m_dataList, m_loopItemDataType ,index);
    }
    /// <summary>
    /// 刷新所有数据
    /// </summary>
    public void RefreshAllItem() {
        //item数量大于数据数量时，数据数量大于item数量属于正常
        if (m_loopItemList.Count > m_dataList.Count) {
            for(int i= m_loopItemList.Count-1; i > m_dataList.Count; i--) {
                RemoveItem(i);
            }
        }

        for (int i = 0; i < m_loopItemList.Count; i++) {
            m_loopItemList[i].FillItem(m_dataList[m_loopItemList[i].itemIndex]);
        }
    }
    /// <summary>
    /// 刷新单个item，如果有m_dataList变化，请使用RefreshAllItem
    /// </summary>
    /// <param name="index">数据m_dataList的下标</param>
    public void RefreshItem(int index) {
        for (int i = 0; i < m_loopItemList.Count; i++) {
            if (index == i) {
                m_loopItemList[i].FillItem(m_dataList[index]);
            }
        }
    }
    #endregion

    #region private
    /// <summary>
    /// 滑动回调
    /// </summary>
    /// <param name="v2"></param>
    private void ScrollCallback(Vector2 v2) {
        Debug.Log(v2);
        UpdateItems();
    }
    private void UpdateItems() {
        if (m_scrollRect.vertical) {
            float offsetY = m_startV2.y - m_contentRectTrans.anchoredPosition.y;
            if (offsetY > 0) {
                Debug.Log("向下滑");
                MoveDown();
            } else {
                Debug.Log("向上滑");
                MoveUp();
            }
        }
        if (m_scrollRect.horizontal) {
            float offsetX = m_startV2.x - m_contentRectTrans.anchoredPosition.x;
            if (offsetX > 0) {
                Debug.Log("向左滑");
                MoveLeft();
            }
            else {
                Debug.Log("向右滑");
                MoveRight();
            }
        }
        m_startV2 = m_contentRectTrans.anchoredPosition;
    } 
    private void MoveUp() {
        for (int i = 0; i < m_loopItemList.Count; i++) {
            //确定上边的item是否超出显示范围(contentRT.anchoredPosition.y在变化)
            if (m_loopItemList[i].itemRT.anchoredPosition.y + m_contentRectTrans.anchoredPosition.y > m_itemHeight) {
                int floorIndex = GetFloorItemIndex();
                //判断有没有到最后一个
                if (m_dataList.Count > floorIndex + 1) {
                    //赋值
                    m_loopItemList[i].itemIndex = floorIndex + 1;
                    SetItemData(m_loopItemList[i]);
                }
            }
        }
    }
    private void MoveDown() {
        for (int i = 0; i < m_loopItemList.Count; i++) {
            //确定下边的item是否超出显示范围(contentRT.anchoredPosition.y在变化)
            if (m_loopItemList[i].itemRT.anchoredPosition.y + m_contentRectTrans.anchoredPosition.y + m_scrollRectTrans.sizeDelta.y < 0) {
                int ceilIndex = GetCeilItemIndex();
                if (ceilIndex>0) {
                    //赋值
                    m_loopItemList[i].itemIndex = ceilIndex - 1;
                    SetItemData(m_loopItemList[i]);
                }
            }
        }
    }
    private void MoveLeft() {
        for (int i = 0; i < m_loopItemList.Count; i++) {
            //确定上边的item是否超出显示范围(contentRT.anchoredPosition.x在变化)
            if (m_loopItemList[i].itemRT.anchoredPosition.x + m_contentRectTrans.anchoredPosition.x < -m_itemWight) {
                int floorIndex = GetFloorItemIndex();
                //判断有没有到最后一个
                if (m_dataList.Count > floorIndex + 1) {
                    //赋值
                    m_loopItemList[i].itemIndex = floorIndex + 1;
                    SetItemData(m_loopItemList[i]);
                }
            }
        }
    }
    private void MoveRight() {
        for (int i = 0; i < m_loopItemList.Count; i++) {
            //确定下边的item是否超出显示范围(contentRT.anchoredPosition.x在变化)
            if (m_loopItemList[i].itemRT.anchoredPosition.x + m_contentRectTrans.anchoredPosition.x - m_scrollRectTrans.sizeDelta.x > 0) {
                int ceilIndex = GetCeilItemIndex();
                if (ceilIndex > 0) {
                    //赋值
                    m_loopItemList[i].itemIndex = ceilIndex - 1;
                    SetItemData(m_loopItemList[i]);
                }
            }
        }
    }
    private void CreateInitItem() {
        if(m_startIndex >= m_dataList.Count) {
            Debug.LogError("m_startIndex is too large");
            return;
        }
        //判断实际生成item的范围
        int initItemNum = m_fillViewItemNum + m_preparativeItemNum;
        initItemNum = m_dataList.Count > initItemNum ? initItemNum : m_dataList.Count;
        //实际生成的item的第一个
        int beginIndex = 0;
        if (m_startIndex > m_dataList.Count - initItemNum) {
            beginIndex = m_dataList.Count - initItemNum;
        }else {
            beginIndex = m_startIndex;
        }
        //考虑多行操作，初始index一定是要整除m_multiLine
        if(beginIndex % m_multiLine > 0) {
            int spaceNum = beginIndex % m_multiLine;
            beginIndex = beginIndex - spaceNum;
            //这种情况下就多实例化几个m_preparativeItemNum，做后备，正好应对多行但是尾行不满的情况
            Debug.Log("这种情况下就多实例化几个m_preparativeItemNum，做后备，正好应对多行但是尾行不满的情况");
            m_preparativeItemNum += spaceNum;
            initItemNum += spaceNum;
        }
        
        //这里使用分帧加载
        StartCoroutine(CreateItemCoroutine(beginIndex, initItemNum));
        
       
    }
    /// <summary>
    /// 分帧加载协程
    /// </summary>
    /// <param name="begin"></param>
    /// <param name="initNum"></param>
    /// <param name="isPos">是否重新定位</param>
    /// <returns></returns>
    private IEnumerator CreateItemCoroutine(int begin, int initNum, bool isPos = true) {
        //item加载以前滑动禁用
        m_scrollRect.enabled = false;
        //开始的item和定位的item在没在一行，看是不是最后一个版面显示了，
        if (begin / m_multiLine == m_startIndex / m_multiLine) {
            for (int i = begin; i < begin + initNum; i++) {
                CreateItem(i);
                if (isPos && i == begin) {
                    //第一个item生成出来以后，就定位一下，设置Content位置，是m_startIndex显示出来
                    SetContentPos(m_startIndex);
                }
                yield return new WaitForEndOfFrame();
            }
        }
        else {
            for (int i = begin + initNum - 1; i >= begin; i--) {
                CreateItem(i);
                if (isPos && i == begin + initNum - 1) {
                    //定位到数据最后几个的时候，加载完需要重新定位一下，防止第一次定位时m_startIndex还未创建出来=====================这种情况使用从后向前生成就好了
                    SetContentPos(m_startIndex);
                }
                yield return new WaitForEndOfFrame();
            }
        }

        //item加载以后滑动启用
        m_scrollRect.enabled = true;

        yield break;
    }
    /// <summary>
    /// 设置滑动Content的大小
    /// </summary>
    private void SetContentSize() {
        float viewSize = 0;
        //返回的是数组下标，所以要+1
        if (m_scrollRect.vertical) {
            viewSize = m_itemHeight * Mathf.CeilToInt((GetFloorItemIndex() + 1) / (float)m_multiLine);
            m_contentRectTrans.sizeDelta = new Vector2(m_scrollRectTrans.sizeDelta.x, viewSize);
        }else if (m_scrollRect.horizontal) {
            viewSize = m_itemWight * Mathf.CeilToInt((GetFloorItemIndex() + 1) / (float)m_multiLine);
            m_contentRectTrans.sizeDelta = new Vector2(viewSize, m_scrollRectTrans.sizeDelta.y);
        }
    }
    /// <summary>
    /// 设置Content位置，使下标为index的item显示出来
    /// 此函数调用要在SetContentSize以后
    /// </summary>
    /// <param name="index"></param>
    private void SetContentPos(int index) {
        if(index > m_dataList.Count - m_fillViewItemNum) {
            //拉到最后
            if (m_scrollRect.vertical) {
                m_contentRectTrans.anchoredPosition = new Vector2(0, m_contentRectTrans.sizeDelta.y - m_scrollRectTrans.sizeDelta.y);
            }
            else if (m_scrollRect.horizontal) {
                m_contentRectTrans.anchoredPosition = new Vector2(m_scrollRectTrans.sizeDelta.x - m_contentRectTrans.sizeDelta.x, 0);
            }
        }
        else {
            //index放在最上边一行
            //content定位
            if (m_scrollRect.vertical) {
                m_contentRectTrans.anchoredPosition = new Vector2(0, Mathf.FloorToInt(index / (float)m_multiLine) * m_itemHeight);
            }
            else if (m_scrollRect.horizontal) {
                m_contentRectTrans.anchoredPosition = new Vector2(Mathf.FloorToInt(index / (float)m_multiLine) * -m_itemWight, 0);
            }
        }
    }
    /// <summary>
    /// 生成item
    /// </summary>
    /// <param name="index"></param>
    private void CreateItem(int index) {
        if(this.transform == null) {
            return;
        }
        //生成item
        GameObject go = (GameObject)GameObject.Instantiate(m_item);
        RectTransform rt = go.GetComponent<RectTransform>();
        rt.SetParent(this.transform);
        rt.pivot = new Vector2(0f, 1f);//pivot左上
        rt.anchorMin = new Vector2(0, 1);//anchor设定为左上,设定为上就行，top就好
        rt.anchorMax = new Vector2(0, 1);
        rt.sizeDelta = m_grid.cellSize;//设置大小
        
        rt.localRotation = Quaternion.identity;
        rt.localScale = Vector3.one;

        LoopItemBase loop = rt.GetComponent(m_loopItemDataType) as LoopItemBase;
        if (loop == null) {
            loop = rt.gameObject.AddComponent(m_loopItemDataType) as LoopItemBase;
        }
        loop.itemIndex = index;
        loop.itemRT = rt;
        loop.FindItem();
        m_loopItemList.Add(loop);

        SetItemData(loop);
    }
    /// <summary>
    /// 确定位置，填充数据，设置拖动背景大小
    /// </summary>
    /// <param name="itemdata"></param>
    private void SetItemData(LoopItemBase itemdata) {
        //确定位置
        float x = 0;
        float y = 0;
        if (m_scrollRect.vertical) {
            if (m_multiLine > 1) {
                x = (itemdata.itemIndex % m_multiLine) * m_itemWight;
            }else {
                x = 0;
            }
            y = itemdata.itemIndex / m_multiLine * -m_itemHeight;
        }else if (m_scrollRect.horizontal) {
            if (m_multiLine > 1) {
                y = (itemdata.itemIndex % m_multiLine) * -m_itemHeight;
            }
            else {
                y = 0;
            }
            x = itemdata.itemIndex / m_multiLine * m_itemWight;
        }
        itemdata.itemRT.anchoredPosition = new Vector2( x, y);//设置位置要区分很多情况,现在是竖直方向，从上到下的情况
        //填充数据
        itemdata.FillItem(m_dataList[itemdata.itemIndex]);
        //设置拖动背景大小
        SetContentSize();
    }
    /// <summary>
    /// 获得存在的itemGameObject最下边一个的索引
    /// </summary>
    /// <returns></returns>
    private int GetFloorItemIndex() {
        int floor = 0;
        for(int i=0;i< m_loopItemList.Count;i++) {
            if(m_loopItemList[i].itemIndex > floor) {
                floor = m_loopItemList[i].itemIndex;
            }
        }
        return floor;
    }
    /// <summary>
    /// 获得存在的itemGameObject最上边一个的索引
    /// </summary>
    /// <returns></returns>
    private int GetCeilItemIndex() {
        int ceil = m_loopItemList[0].itemIndex;
        for (int i = 0; i < m_loopItemList.Count; i++) {
            if (m_loopItemList[i].itemIndex < ceil) {
                ceil = m_loopItemList[i].itemIndex;
            }
        }
        return ceil;
    }
    /// <summary>
    /// 删除所有item
    /// </summary>
    private void RemoveAllItem() {
        List<Transform> transList = new List<Transform>();
        for (int i = 0; i < m_loopItemList.Count; i++) {
            transList.Add(m_loopItemList[i].itemRT.transform);
        }
        m_loopItemList.Clear();
        for (int i = 0; i < transList.Count; i++) {
            Destroy(transList[i]); ;
        }
        transList.Clear();
    }
    /// <summary>
    /// 移除m_loopItemList中的一个
    /// </summary>
    /// <param name="index"></param>
    private void RemoveItem(int index) {
        Transform trans = m_loopItemList[index].itemRT.transform;

        m_loopItemList.RemoveAt(index);
        Destroy(trans);
    }
    #endregion
}
public class LoopItemBase : MonoBehaviour{
    public RectTransform itemRT;
    public int itemIndex;
    public virtual void FindItem() { }
    public virtual void FillItem(object data) {
        itemRT.transform.name = itemIndex.ToString();
        itemRT.Find("Text").GetComponent<Text>().text = itemIndex.ToString();
    }
}
